package spitfire.ksim.algorithm;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import spitfire.ksim.data.KNodeSnapshot;
import spitfire.ksim.data.KSensorSnapshot;

public class FuzzyRuleBase {

	private Map<String, List<FuzzyEntry>> typeEntryMap = new HashMap<String, List<FuzzyEntry>>(6);
	private String algorithmSpec;
	private RequestAlgorithm reqAlgo;
	private RuleExtractionAlgorithm extrAlgo = new RuleExtractionImpl();
	private List<FuzzyDBUpdatedListener> updatedListeners = 
		Collections.synchronizedList(new ArrayList<FuzzyDBUpdatedListener>());
	
	public void setRequestAlgorithm(String algorithmSpec) {
		this.algorithmSpec = algorithmSpec;
		System.out.println("Using " + algorithmSpec + " RuleBase algorithm");
		reqAlgo = RequestAlgoFactory.getRequestAlgorithm(algorithmSpec);
	}

	public String getRequestAlgorithmName() {
		return algorithmSpec;
	}

	public List<FuzzyEntry> getFuzzyEntryListOf(String sensorType) {
		synchronized (typeEntryMap) {
			return typeEntryMap.get(sensorType);
		}
	}
	
	public List<FuzzyEntry> getFuzzyEntryList() {
		ArrayList<FuzzyEntry> list = new ArrayList<FuzzyEntry>();
		synchronized (typeEntryMap) {
			for (List<FuzzyEntry> typeEntryList : typeEntryMap.values()) {
				list.addAll(typeEntryList);
			}
		}
		return list;
	}
	
	public boolean containsSensorType(String sensorType) {
		synchronized (typeEntryMap) {
			return typeEntryMap.containsKey(sensorType);
		}
	}
	
	public String requestForSD(KNodeSnapshot snapshot) {
		if (reqAlgo == null) {
			throw new NullPointerException("Request algorithm hasn't been set");
		}
		synchronized (typeEntryMap) {
			return reqAlgo.requestForSD(this, snapshot);
		}
	}

	public void updateRuleBase(KNodeSnapshot snapshot, String sd, String nodeId) {
		Map<String, KSensorSnapshot> typeSnrSnapShotMap = snapshot.getSensorSnapshotMap();
		synchronized (typeEntryMap) {
			for (Entry<String, KSensorSnapshot> entry : typeSnrSnapShotMap.entrySet()) {
				String sensorType = entry.getKey();
				List<Long> timeStampList = entry.getValue().getTimeStampList();
				List<Double> dataList = entry.getValue().getDataList();
				FuzzyRule rule = extrAlgo.extractRule(timeStampList, dataList);
				if (rule != null) {
					updateRuleBase(nodeId, sensorType, sd, rule);
				}
			}
		}
	}
	
	public void updateRuleBase(String nodeId, String sensorType, String sd, FuzzyRule rule) {
		if (rule == null || nodeId == null || sensorType == null || sd == null) {
			throw new NullPointerException();
		}
		List<FuzzyEntry> entryList = null;
		if (typeEntryMap.containsKey(sensorType)) {
			// sensor type exists
			entryList = typeEntryMap.get(sensorType);
		} else {
			// sensor type doesn't exist 
			entryList = new ArrayList<FuzzyEntry>();
			typeEntryMap.put(sensorType, entryList);
		}
		
		for (FuzzyEntry entry : entryList) {
			if (entry.match(nodeId, sensorType)) {
				// Fuzzy entry exists
				entry.setSd(sd);
				entry.setRule(rule);
				invokeFuzzyDBUpdated();
				return;
			}
		}
		// Fuzzy entry doesn't exist
		entryList.add(new FuzzyEntry(nodeId, sensorType, sd, rule));
		invokeFuzzyDBUpdated();
	}
	
	public void addFuzzyDBUpdatedListener(FuzzyDBUpdatedListener l) {
		updatedListeners.add(l);
	}
	
	public void removeFuzzyDBUpdatedListener(FuzzyDBUpdatedListener l) {
		updatedListeners.remove(l);
	}
	
	public void invokeFuzzyDBUpdated() {
		synchronized (updatedListeners) {
			for (FuzzyDBUpdatedListener l : updatedListeners) {
				l.fuzzyDBUpdated();
			}
		}
	}
}
